D:\a\tools.proto\tools.proto\compiler\src\gen\template\util.rs
Line | Count | Source |
1 | | // Copyright (c) 2025, BlockProject 3D |
2 | | // |
3 | | // All rights reserved. |
4 | | // |
5 | | // Redistribution and use in source and binary forms, with or without modification, |
6 | | // are permitted provided that the following conditions are met: |
7 | | // |
8 | | // * Redistributions of source code must retain the above copyright notice, |
9 | | // this list of conditions and the following disclaimer. |
10 | | // * Redistributions in binary form must reproduce the above copyright notice, |
11 | | // this list of conditions and the following disclaimer in the documentation |
12 | | // and/or other materials provided with the distribution. |
13 | | // * Neither the name of BlockProject 3D nor the names of its contributors |
14 | | // may be used to endorse or promote products derived from this software |
15 | | // without specific prior written permission. |
16 | | // |
17 | | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
18 | | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
19 | | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
20 | | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
21 | | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
22 | | // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
23 | | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
24 | | // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
25 | | // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
26 | | // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
27 | | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 | | |
29 | | use itertools::Itertools; |
30 | | use regex::Regex; |
31 | | use std::borrow::Cow; |
32 | | use bp3d_util::string::BufTools; |
33 | | |
34 | | #[allow(clippy::enum_variant_names)] |
35 | | enum Convention { |
36 | | PascalCase, |
37 | | SnakeCase, |
38 | | ScreamingCase, |
39 | | } |
40 | | |
41 | 136 | fn guess_case_convention(s: &str) -> Convention { |
42 | | //Assume all strings are Rust identifiers following standard Rust conventions: |
43 | | // snake_case, PascalCase and SCREAMING_CASE. |
44 | 136 | let upper1 = (s.as_bytes()[0] >= b'A' && s.as_bytes()[0] <= b'Z') || s44 .as_bytes()[0] == b'_'; Branch (44:19): [True: 136, False: 0]
Branch (44:46): [True: 92, False: 44]
Branch (44:19): [True: 0, False: 0]
Branch (44:46): [True: 0, False: 0]
|
45 | 136 | let upper2 = (s.as_bytes()[s.as_bytes().len() - 1] >= b'A' && s94 .as_bytes94 ()[s.as_bytes().len() - 1] <= b'Z') Branch (45:19): [True: 94, False: 42]
Branch (45:67): [True: 8, False: 86]
Branch (45:19): [True: 0, False: 0]
Branch (45:67): [True: 0, False: 0]
|
46 | 128 | || s.as_bytes()[s.as_bytes().len() - 1] == b'_'; |
47 | 136 | if upper1 && upper292 { Branch (47:8): [True: 92, False: 44]
Branch (47:18): [True: 8, False: 84]
Branch (47:8): [True: 0, False: 0]
Branch (47:18): [True: 0, False: 0]
|
48 | 8 | Convention::ScreamingCase |
49 | 128 | } else if upper1 { Branch (49:15): [True: 84, False: 44]
Branch (49:15): [True: 0, False: 0]
|
50 | 84 | Convention::PascalCase |
51 | | } else { |
52 | 44 | Convention::SnakeCase |
53 | | } |
54 | 136 | } |
55 | | |
56 | 44 | fn capitalize(value: &str) -> Cow<str> { |
57 | 44 | match value.as_bytes().capitalise_ascii() { |
58 | 0 | Cow::Borrowed(v) => String::from_utf8_lossy(v), |
59 | 44 | Cow::Owned(v) => String::from(&*String::from_utf8_lossy(&*v)).into() |
60 | | } |
61 | 44 | } |
62 | | |
63 | 0 | fn decapitalize(value: &str) -> Cow<str> { |
64 | 0 | match value.as_bytes().decapitalise_ascii() { |
65 | 0 | Cow::Borrowed(v) => String::from_utf8_lossy(v), |
66 | 0 | Cow::Owned(v) => String::from(&*String::from_utf8_lossy(&*v)).into() |
67 | | } |
68 | 0 | } |
69 | | |
70 | | pub trait CaseConversion<'a> { |
71 | | fn to_pascal_case(self) -> Cow<'a, str>; |
72 | | fn to_snake_case(self) -> Cow<'a, str>; |
73 | | fn to_camel_case(self) -> Cow<'a, str>; |
74 | | fn to_screaming_case(self) -> Cow<'a, str>; |
75 | | } |
76 | | |
77 | | struct SnakeCase<'a>(&'a str); |
78 | | struct PascalCase<'a>(&'a str); |
79 | | struct ScreamingCase<'a>(&'a str); |
80 | | |
81 | | impl<'a> CaseConversion<'a> for SnakeCase<'a> { |
82 | 44 | fn to_pascal_case(self) -> Cow<'a, str> { |
83 | 44 | self.0.split("_").map(capitalize).join("").into() |
84 | 44 | } |
85 | | |
86 | 0 | fn to_snake_case(self) -> Cow<'a, str> { |
87 | 0 | self.0.into() |
88 | 0 | } |
89 | | |
90 | 0 | fn to_camel_case(self) -> Cow<'a, str> { |
91 | 0 | self.0 |
92 | 0 | .split("_") |
93 | 0 | .enumerate() |
94 | 0 | .map(|(i, v)| if i != 0 { capitalize(v) } else { v.into() }) Branch (94:30): [True: 0, False: 0]
Branch (94:30): [True: 0, False: 0]
|
95 | 0 | .join("") |
96 | 0 | .into() |
97 | 0 | } |
98 | | |
99 | 0 | fn to_screaming_case(self) -> Cow<'a, str> { |
100 | 0 | self.0.split("_").map(|v| v.to_uppercase()).join("_").into() |
101 | 0 | } |
102 | | } |
103 | | |
104 | | impl<'a> CaseConversion<'a> for PascalCase<'a> { |
105 | 0 | fn to_pascal_case(self) -> Cow<'a, str> { |
106 | 0 | self.0.into() |
107 | 0 | } |
108 | | |
109 | 28 | fn to_snake_case(self) -> Cow<'a, str> { |
110 | 28 | let regex = Regex::new("[A-Z]([a-z]|[0-9])*").unwrap(); |
111 | 36 | let useless28 = regex28 .find_iter28 (self.028 ).map28 (|v| v.as_str().to_lowercase()).join28 ("_"28 ).into28 (); |
112 | 28 | useless |
113 | 28 | } |
114 | | |
115 | 0 | fn to_camel_case(self) -> Cow<'a, str> { |
116 | 0 | decapitalize(self.0) |
117 | 0 | } |
118 | | |
119 | 56 | fn to_screaming_case(self) -> Cow<'a, str> { |
120 | 56 | let regex = Regex::new("[A-Z]([a-z]|[0-9])*").unwrap(); |
121 | 86 | let useless56 = regex56 .find_iter56 (self.056 ).map56 (|v| v.as_str().to_uppercase()).join56 ("_"56 ).into56 (); |
122 | 56 | useless |
123 | 56 | } |
124 | | } |
125 | | |
126 | | impl<'a> CaseConversion<'a> for ScreamingCase<'a> { |
127 | 0 | fn to_pascal_case(self) -> Cow<'a, str> { |
128 | 0 | self.0.split("_").map(|v| capitalize(&v.to_lowercase()).to_string()).join("").into() |
129 | 0 | } |
130 | | |
131 | 4 | fn to_snake_case(self) -> Cow<'a, str> { |
132 | 4 | self.0.to_lowercase().into() |
133 | 4 | } |
134 | | |
135 | 0 | fn to_camel_case(self) -> Cow<'a, str> { |
136 | 0 | self.0 |
137 | 0 | .split("_") |
138 | 0 | .enumerate() |
139 | 0 | .map(|(i, v)| { |
140 | 0 | if i != 0 { Branch (140:20): [True: 0, False: 0]
Branch (140:20): [True: 0, False: 0]
|
141 | 0 | format!("{}", capitalize(&v.to_lowercase())) |
142 | | } else { |
143 | 0 | v.into() |
144 | | } |
145 | 0 | }) |
146 | 0 | .join("") |
147 | 0 | .into() |
148 | 0 | } |
149 | | |
150 | 4 | fn to_screaming_case(self) -> Cow<'a, str> { |
151 | 4 | self.0.into() |
152 | 4 | } |
153 | | } |
154 | | |
155 | | macro_rules! impl_case_conversion { |
156 | | ($s: expr, $func: ident) => { |
157 | | match guess_case_convention($s) { |
158 | | Convention::PascalCase => PascalCase($s).$func(), |
159 | | Convention::SnakeCase => SnakeCase($s).$func(), |
160 | | Convention::ScreamingCase => ScreamingCase($s).$func(), |
161 | | } |
162 | | }; |
163 | | } |
164 | | |
165 | | impl<'a> CaseConversion<'a> for &'a str { |
166 | 44 | fn to_pascal_case(self) -> Cow<'a, str> { |
167 | 44 | impl_case_conversion!(self0 , to_pascal_case) |
168 | 44 | } |
169 | | |
170 | 32 | fn to_snake_case(self) -> Cow<'a, str> { |
171 | 32 | impl_case_conversion!(self28 , to_snake_case) |
172 | 32 | } |
173 | | |
174 | 0 | fn to_camel_case(self) -> Cow<'a, str> { |
175 | 0 | impl_case_conversion!(self, to_camel_case) |
176 | 0 | } |
177 | | |
178 | 60 | fn to_screaming_case(self) -> Cow<'a, str> { |
179 | 60 | impl_case_conversion!(self56 , to_screaming_case) |
180 | 60 | } |
181 | | } |